import pymysql
from pymysql.cursors import DictCursor
from pymysql import OperationalError, ProgrammingError
from longguo_ui.config.config import config
from longguo_ui.utils.logger import logger
from longguo_ui.utils.retry_utils import RetryUtils


class DBUtils:
    """
    数据库操作工具类，封装数据库连接管理、查询、更新等核心操作
    支持自动重连、连接状态检查、上下文管理（自动关闭连接）等功能
    所有操作均包含详细日志记录和异常处理
    """

    def __init__(self):
        """初始化数据库工具实例，建立数据库连接"""
        self.conn = None  # 数据库连接对象
        self.cursor = None  # 游标对象
        # 初始化重试工具：最多重试3次，初始延迟1秒，指数退避（每次延迟翻倍）
        self.retry_utils = RetryUtils(max_retries=3, delay=1, backoff=2)
        # 使用带重试机制的方式建立初始连接（处理临时网络问题）
        self.retry_utils.execute_with_retry(self.connect, exceptions=(OperationalError,))

    def connect(self):
        """
        建立数据库连接（支持自动关闭已有连接）
        连接参数从配置文件读取，使用DictCursor返回字典格式结果

        :raises OperationalError: 连接失败（网络问题或配置错误）
        :raises Exception: 其他连接异常
        """
        try:
            # 关闭已有连接，避免连接泄露
            if self.conn:
                self.close()

            # 建立新连接
            self.conn = pymysql.connect(
                host=config.DB_HOST,  # 数据库主机地址
                port=config.DB_PORT,  # 数据库端口
                user=config.DB_USER,  # 数据库用户名
                password=config.DB_PASSWORD,  # 数据库密码
                database=config.DB_NAME,  # 数据库名称
                charset='utf8mb4',  # 字符编码（支持emoji）
                cursorclass=DictCursor  # 游标返回字典格式（键为字段名）
            )
            self.cursor = self.conn.cursor()  # 创建游标对象
            logger.info(f"数据库连接成功: {config.DB_HOST}:{config.DB_PORT}/{config.DB_NAME}")
        except OperationalError as e:
            logger.error(f"数据库连接失败（网络/配置问题）: {str(e)}")
            raise  # 抛出异常供上层处理
        except Exception as e:
            logger.error(f"数据库连接异常: {str(e)}")
            raise

    def _check_connection(self):
        """
        检查数据库连接有效性，若连接断开则自动重新连接
        内部使用ping()方法验证连接，失败时调用connect()重建连接
        """
        try:
            # 不自动重连，由本类手动处理
            self.conn.ping(reconnect=False)
        except:
            logger.warning("数据库连接已断开，尝试重新连接...")
            self.connect()  # 重建连接

    def execute_query(self, sql, params=None):
        """
        执行查询类SQL语句（SELECT），支持参数化查询防止SQL注入

        :param sql: 查询SQL语句（使用%s作为参数占位符）
        :param params: SQL参数（元组/列表），可选，默认空
        :return: 查询结果列表，每个元素为字典（键:字段名，值:字段值）
        :raises ProgrammingError: SQL语法错误时抛出
        :raises Exception: 其他查询相关异常时抛出
        """
        self._check_connection()  # 执行前确保连接有效
        try:
            # 执行SQL（参数化查询）
            self.cursor.execute(sql, params or ())
            # 获取所有查询结果
            result = self.cursor.fetchall()
            logger.info(f"执行查询成功: SQL={sql}, 参数={params}, 结果行数={len(result)}")
            return result
        except ProgrammingError as e:
            logger.error(f"SQL语法错误: {str(e)}, SQL={sql}")
            raise
        except Exception as e:
            logger.error(f"查询执行失败: {str(e)}")
            self.conn.rollback()  # 回滚事务（虽然查询通常无需回滚，保险起见）
            raise

    def execute_update(self, sql, params=None):
        """
        执行更新类SQL语句（INSERT/UPDATE/DELETE），支持事务管理

        :param sql: 更新SQL语句（使用%s作为参数占位符）
        :param params: SQL参数（元组/列表），可选，默认空
        :return: 受影响的行数
        :raises Exception: 执行异常时抛出（包含事务回滚）
        """
        self._check_connection()  # 执行前确保连接有效
        try:
            # 执行SQL并返回受影响行数
            affected_rows = self.cursor.execute(sql, params or ())
            self.conn.commit()  # 提交事务
            logger.info(f"执行更新成功: SQL={sql}, 参数={params}, 影响行数={affected_rows}")
            return affected_rows
        except Exception as e:
            logger.error(f"更新执行失败: {str(e)}")
            self.conn.rollback()  # 执行失败时回滚事务
            raise

    def close(self):
        """
        关闭数据库连接资源（先关闭游标，再关闭连接）
        包含异常捕获，避免关闭操作本身出错
        """
        # 关闭游标
        if self.cursor:
            try:
                self.cursor.close()
                logger.info("数据库游标已关闭")
            except Exception as e:
                logger.warning(f"游标关闭失败: {str(e)}")  # 非致命错误，仅警告

        # 关闭连接
        if self.conn:
            try:
                self.conn.close()
                logger.info("数据库连接已关闭")
            except Exception as e:
                logger.warning(f"连接关闭失败: {str(e)}")  # 非致命错误，仅警告

    def __enter__(self):
        """
        上下文管理器入口方法，支持with语句
        使用方式: with DBUtils() as db: ...

        :return: 当前DBUtils实例
        """
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        上下文管理器出口方法，自动关闭连接
        若发生异常，会记录异常信息

        :param exc_type: 异常类型
        :param exc_val: 异常值
        :param exc_tb: 异常追踪信息
        """
        self.close()  # 自动关闭连接
        # 记录异常信息（若有）
        if exc_type:
            logger.error(f"数据库操作异常: {exc_type.__name__} - {exc_val}")